agora inbox for [email protected]  
help / color / mirror / Atom feed
RFC: extension_search_path to supplement dynamic_library_path
269+ messages / 2 participants
[nested] [flat]

* RFC: extension_search_path to supplement dynamic_library_path
@ 2020-11-30 03:23 Craig Ringer <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Craig Ringer @ 2020-11-30 03:23 UTC (permalink / raw)
  To: pgsql-hackers; +Cc: Robert Haas <[email protected]>; Bruce Momjian <[email protected]>

Hi all

We have a dynamic_library_path setting to allow postgres to search
additional locations for loadable libraries (since 8.1, commit
761a0bb69b). This permits libraries to be located in alternate
locations for loading by shared_preload_libraries, LOAD commands,
implicit loading via function calls etc. This searches '$libdir',
PG_INSTALL_DIR/lib, by default, but can be modified by configuration.

When extensions were introduced in 9.1 (d9572c4e3b) no search path
support for extension control files and SQL scripts was added to match
dynamic_library_path. The only place extension scripts and control
files can be placed is PG_INSTALL_DIR/share/extension .

Practically all loadable modules now come with extension control files
and usually extension SQL scripts. In practice this means extensions
are not relocatable.

Reasons to care are much the same reasons dynamic_library_path was
added. Specific use cases include:

1. Running "make check" against a temporary instance of a postgres
install (from packages or source) without needing permission to modify
the installation's lib/ and share/extension directories or install the
extension before testing it.
2. Letting postgres builds installed in non-default paths load
extensions installed from public repositories like yum.postgresql.org,
so long as the custom build can promise to be ABI-compatible with the
mainline builds.


------

Details:

(1) is useful when building and testing extensions against a postgres
that was installed from packages, or when you want to build and check
a new or updated extension before installing it in the local postgres.

Right now the extension must "make install" into the active postgres
install first; we have to install the built code before testing it,
which is backwards. And the extension tests need have write privileges
to lib/ and share/extension which is undesirable.

PGXS doesn't support a "temp_install" mode, so we can't "make check"
the same way we do for in-tree builds, and it doesn't IMO make a lot
of sense to do so. It'd be much better to just allow pg_regress,
pg_isolation_regress and TAP tests to specify an extension_search_path
when they initdb and start a new temp instance.

(2) is useful when for an alternative PostgreSQL build that's
binary-compatible with normal builds in a non-default path, while
continuing to use extensions packaged from public repos. For example,
to hot-fix a production issue with a patched build until the next
postgres release includes the fix without overwriting packaged files.
Or to trial a proposed upgrade before committing to it. Right now if
you want to install to a non-default path you have to recompile all
your extensions or manually copy files around. Not fun if you use
(say) PostGIS.

I'm mainly interested in the first use case.





^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 269+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 269+ messages in thread


end of thread, other threads:[~2025-08-29 14:12 UTC | newest]

Thread overview: 269+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-11-30 03:23 RFC: extension_search_path to supplement dynamic_library_path Craig Ringer <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox