Thread

  1. [PATCH v1 2/4] Introduce trenary reloptions

    Nikolay Shaplov <dhyan@nataraj.su> — 2025-08-29T14:12:52Z

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